package skipList_02;

import general_algorithms.Hashing;
import general_algorithms.Hashing_algorithms;

import java.math.BigInteger;
import java.security.NoSuchAlgorithmException;
import java.util.Stack;
/**
 * @author Tobias Hardes Aug 12, 2014 10:24:23 PM Class for the calculation of
 *         the hash values that are needed for the proofgeneration
 */

public final class SkipListAuthentication {
	/*********************************************************
	 * 1. u = null, i.e., v is on the base level: * If w is a tower node, then
	 * f(v) = h(elem(v), elem(w)).* If w is a plateau node, then f(v) =
	 * h(elem(v), f(w)). *
	 ********************************************************/

	/*********************************************************
	 * 2. u = null, i.e., v is not on the base level: * If w is a tower node,
	 * then f(v) = f(u). * If w is a plateau node, then f(v) = h(f(u), f(w)). *
	 ********************************************************/
	
	/**
	 * Calculates the hash values for all nodes in the skip list according to the structural information
	 * 
	 * @param l The skip list to calculate the data
	 */
	protected static SkipList calculateAuthentication2(SkipList l) {

		for (int x = 0; x < l.nodes.size(); x++) {// DO the calculation for
													// every node

			SkipListSearchResult res = l.search(l.nodes.get(x));
			Stack<SkipListNode> st = res.getSt();

			SkipListNode currentNode = null;;// element to search for
			SkipListNode tmpNode = null;
			while (!st.empty())// Pop from stack until all nodes were executed
			{
				currentNode = st.pop();
				
				//Check the level of the node
				if(currentNode.level == 0)//Base level node
				{			
					currentNode.hashValue = value_BaseNode(currentNode);
					UpdateSkipList(currentNode, l);
					
				}
				if(currentNode.maxLevel > 0)
				{
					for(int i = 1; i <=currentNode.maxLevel; i++)//Do the calculation for the upper nodes
					{
						if(currentNode.key == Integer.MIN_VALUE)
							break;
						currentNode.hashValue = value_NonBaseNode(currentNode.down[i],currentNode);
						UpdateSkipList(currentNode, l);
					}
				}
			}
		}
		return l;
	}

	/*protected static SkipList calculateAuthentication(SkipList l) {
		
		for (int x = 0; x < l.nodes.size(); x++) {

			SkipListSearchResult res = l.search(l.nodes.get(x));
			Stack<SkipListNode> st = res.getSt();
			// st.pop(); // remove inf node
			SkipListNode maxValue = st.pop();
			SkipListNode tmpNode = null;

			while (maxValue != null) {
				for (int i = 0; i <= maxValue.maxLevel || maxValue != null; i++) {

					if (maxValue.level == 0)// Base node
					{
						maxValue.hashValue = value_BaseNode(maxValue);
					}
					l = UpdateSkipList(maxValue, l);
					SkipListNode maxValueTMP = maxValue;
					int k = maxValueTMP.maxLevel;
		
					if (maxValue.key == Integer.MIN_VALUE) {
						try {
							tmpNode = maxValue;
							maxValue = st.pop();
						} catch (EmptyStackException ex) {
							maxValue = null;
							break;
						}
						break;
					}
					if (maxValueTMP.down[k] != null) {
						while (maxValueTMP.down[k].key != 0) {
							if (maxValue.level < maxValue.next[0].maxLevel)// second
																			// case
																			// for
																			// "="
							{
								maxValue.down[k].hashValue = maxValueTMP.hashValue;
								System.out
										.println("Copy value from bottom node");
							} else if (maxValue.level == maxValue.next[0].maxLevel) // If
																					// equal,
																					// do
							{
								maxValue.down[i].hashValue = value_NonBaseNode(maxValue,maxValue);
							} else
								maxValue.down[i].hashValue = value_NonBaseNode(maxValue,maxValue);
							k--;
							if (maxValueTMP.down[k] == null)
								break;
						}
					}
					try {
						tmpNode = maxValue;
						maxValue = st.pop();
					} catch (EmptyStackException ex) {
						maxValue = null;
						break;
					}
				}
			}
		}
		return l;
	}*/
	/**
	 * Updates the skip list nodes with the calculated hash data
	 * 
	 * @param node Node of the skip list to update the hash
	 * @param l Skip list to update
	 * @return The skip list with an updated hash value for the given node
	 */
	private static SkipList UpdateSkipList(SkipListNode node, SkipList l) {
		for (int i = 0; i < l.update.length; i++) {
			for (int k = 0; k < l.update[i].next.length; k++) {

				if (l.update[i].next[k].key == node.key)
					l.update[i].next[k].hashValue = node.hashValue;
			}
		}
		return l;

	}
	/**
	 * Calculates the hash value for a base node --> Level of the node is 0
	 * 
	 * @param node The node of the skiplist to calculate
	 * @return Hash value of the skip list
	 */
	private static byte[] value_BaseNode(SkipListNode node) {
		/*
		 * Calculate f (v) = h(elem(v), elem(w)).
		 */
		if (node.level <= node.next[0].maxLevel)// Tower node --> there is a // node above
		{
			System.out.println("Found a tower node -- base node");
			return node.hashValue = calculateHash(node.key + "",
					node.next[0].key + "");
		}
		/*
		 * Calculate f(v) = h(elem(v), f (right(v))).
		 */
		else // Plateau node
		{
			System.out.println("Found a plateau node -- base node");
			System.out.println(node.next[0].key);
			return node.hashValue = calculateHash(node.key + "",
					Hashing.hashByteToString(node.next[0].hashValue));
		}

		// return Hashing.hashByteToString(node.hashValue);
	}
	/**
	 * Calculates the hash value for a non-base node --> Level of the node > 0
	 * 
	 * @param node The node of the skiplist to calculate
	 * @return Hash value of the skip list
	 */
	private static byte[] value_NonBaseNode(SkipListNode node,SkipListNode currentNode) {
		/*
		 * Calculate f (v) = f(u). ==> Take value from bottom node
		 */
		if (node.level < node.maxLevel)// Tower node --> there is a node above
		{
			System.out.println("Found a tower node -- non base node -- Copy value from bottom");
			return node.hashValue = currentNode.hashValue; //Take the hash from the bottom node
		}
		else // Plateau node
		{
			System.out.println("Found a plateau node -- nonbase node -- Performing crazy hashing");
			System.out.println("Calculating hash for entry " + node.key +" on level "+node.level);
		    return node.hashValue = calculateHash(Hashing.hashByteToString(currentNode.hashValue),
					Hashing.hashByteToString(currentNode.next[node.level].hashValue)); //f(v) = h(f(down(v)), f (right(v)))
		}

	}

	/**
	 * The real calculation of the hash
	 * 
	 * @param value1 First input for hash calculation
	 * @param value2 Second input for hash calculation
	 * @return Hash value of both inputs
	 */
	private static byte[] calculateHash(String value1, String value2) {
		String result = "";
		byte[] hash = null;
		try {
			hash = Hashing.GetHash(Hashing_algorithms.SHA256, value1 + value2);
			BigInteger bigInt = new BigInteger(1, hash);
			result = bigInt.toString(16);
			System.out.println(result);
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		}
		return hash;
	}
}
